# frozen_string_literal: true

class Observable
  def initialize
    @listeners = {}
  end

  def get
    return self[nil]
  end

  def set(value)
    self[nil] = value
  end

  def [](name)
    raise InvalidGetAttribute, name
  end

  def []=(name, _value)
    raise InvalidSetAttribute, name
  end

  def on_event(name = nil, &block)
    return on_event(:default, &block) if name.nil?

    @listeners[name] ||= []
    @listeners[name] << block
  end

  def dispatch_event(name = nil, *args)
    return dispatch_event(:default, *args) if name.nil?

    @listeners[name]&.each do |l|
      l.call(*args)
    end
  end

  class InvalidGetAttribute < RuntimeError
    def initialize(name)
      super("Invalid get attribute #{name} for #{self.class}")
    end
  end

  class InvalidSetAttribute < RuntimeError
    def initialize(name)
      super("Invalid set attribute #{name} for #{self.class}")
    end
  end
end

class ArrayObservable < Observable
  def initialize
    @arr = []
    super()
  end

  def [](name)
    return super unless name.nil?

    return @arr
  end

  def []=(name, value)
    super unless name.nil?

    @arr = value
    dispatch_modification
  end

  MUTABILITY = {
    :clear   => true,
    :replace => true,
    :shift   => true,
    :unshift => true,
    :<<      => true,
    :pop     => true,
    :[]=     => true,
    :any?    => false,
    :empty?  => false,
    :last    => false,
    :first   => false,
    :[]      => false
  }.freeze

  def method_missing(m, *args)
    return super unless respond_to_missing?(m)

    prev = @arr.dup unless MUTABILITY.key?(m)

    return @arr.send(m, *args).tap do |new|
      dispatch_modification if MUTABILITY[m] || (!MUTABILITY.key?(m) && (prev != new))
    end
  end

  def dispatch_modification
    dispatch_event(nil, @arr)
  end

  def respond_to_missing?(m)
    return @arr.respond_to?(m)
  end
end
